{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**输出大小等于输入大小nh × nw减去卷积核大小kh × kw**\n",
    "\n",
    "(nh − kh + 1) × (nw − kw + 1)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[1., 1., 0., 0., 0., 0., 1., 1.],\n",
       "        [1., 1., 0., 0., 0., 0., 1., 1.],\n",
       "        [1., 1., 0., 0., 0., 0., 1., 1.],\n",
       "        [1., 1., 0., 0., 0., 0., 1., 1.],\n",
       "        [1., 1., 0., 0., 0., 0., 1., 1.],\n",
       "        [1., 1., 0., 0., 0., 0., 1., 1.]])"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "X=torch.ones((6,8))\n",
    "X[:,2:6]=0\n",
    "X"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[ 0.,  1.,  0.,  0.,  0., -1.,  0.],\n",
       "        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],\n",
       "        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],\n",
       "        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],\n",
       "        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],\n",
       "        [ 0.,  1.,  0.,  0.,  0., -1.,  0.]])"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Y=torch.tensor([[ 0., 1., 0., 0., 0., -1., 0.],\n",
    "[ 0., 1., 0., 0., 0., -1., 0.],\n",
    "[ 0., 1., 0., 0., 0., -1., 0.],\n",
    "[ 0., 1., 0., 0., 0., -1., 0.],\n",
    "[ 0., 1., 0., 0., 0., -1., 0.],\n",
    "[ 0., 1., 0., 0., 0., -1., 0.]])\n",
    "Y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上X，Y中，-1代表黑色->白色，是一个边缘，同样1代表白色->黑色，也是边缘，</br>\n",
    "0代表没有边缘（亮度没有明显变化）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 学习卷积核"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch1,loss:4.751\n",
      "epoch3,loss:0.858\n",
      "epoch5,loss:0.169\n",
      "epoch7,loss:0.039\n",
      "epoch9,loss:0.011\n"
     ]
    }
   ],
   "source": [
    "from torch import nn\n",
    "import torch\n",
    "\n",
    "#输入1通道，输出1通道，卷积核尺寸1x2【二维卷积层】\n",
    "conv2d=nn.Conv2d(1,1,kernel_size=(1,2),bias=False)\n",
    "\"\"\"样本数，通道，高，宽\"\"\"\n",
    "X=X.reshape((1,1,6,8))\n",
    "Y=Y.reshape((1,1,6,7))\n",
    "lr=3e-2\n",
    "\n",
    "for i in range(10):\n",
    "    Y_hat=conv2d(X)\n",
    "    l=(Y_hat-Y)**2\n",
    "    conv2d.zero_grad()\n",
    "    l.sum().backward()\n",
    "    #迭代卷积核\n",
    "    \"\"\"[:]代表，获取权重张量的所有元素\"\"\"\n",
    "    conv2d.weight.data[:]-=lr*conv2d.weight.grad\n",
    "   \n",
    "    if (i%2)==0:\n",
    "        print(f'epoch{i+1},loss:{l.sum():.3f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[ 0.9860, -0.9999]])"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 原权重[1.0,-1.0],二者接近\n",
    "conv2d.weight.data.shape\n",
    "conv2d.weight.data.reshape((1,2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 填充和步幅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "假设输入形状为nh × nw，卷积核形状为kh × kw，那么输出形\n",
    "状将是(nh − kh + 1) × (nw − kw + 1)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**为什么填充？**</br>\n",
    "一个240 × 240像素的图像，经过10层5 × 5的卷积后，将减少到200 × 200像素。</br>如此一\n",
    "来，原始图像的边界丢失了许多有用信息。而填充是解决此问题最有效的方法；"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**为什么需要步幅？**</br>\n",
    "如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 填充"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在应用多层卷积时，我们常常丢失边缘像素。由于我们通常使用小卷积核，因此对于任何单个卷\n",
    "积，</br>我们可能只会丢失几个像素。但随着我们应用许多连续卷积层，累积丢失的像素数就多了。解决这个问\n",
    "题的简单方法即为填充（padding）：在输入图像的边界填充元素（通常填充元素是0）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义一个计算卷积的函数\n",
    "def comp_conv2d(conv2d,X):\n",
    "    #重置X为一批次，一通道，以及X的像素大小\n",
    "    X=X.reshape((1,1)+X.shape)\n",
    "    Y=conv2d(X)\n",
    "    #省略前两个维度：批量大小，通道\n",
    "    return Y.reshape(Y.shape[2:])\n",
    "\n",
    "\"\"\"每边填充1行or1列，因此共添加2行or2列\"\"\"\n",
    "conv2d=nn.Conv2d(1,1,kernel_size=3,padding=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([8, 8])"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#输出规格：8+2-3+1=>8\n",
    "X=torch.rand(size=(8,8))\n",
    "comp_conv2d(conv2d,X).shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([8, 8])"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#X是(8,8),行添加2x2=4,列添加2x1=2,padding后是(12,10)\n",
    "\"\"\"卷积核是(5,3),输出是(12-5+1,10-3+1)=>(8,8)\"\"\"\n",
    "conv2d=nn.Conv2d(1,1,kernel_size=(5,3),padding=(2,1))\n",
    "comp_conv2d(conv2d,X).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 步幅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在计算互相关时，卷积窗口从输入张量的左上角开始，向下、向右滑动。在前面的例子中，我们默认每次滑动\n",
    "一个元素。</br>但是，有时候为了高效计算或是缩减采样次数，卷积窗口可以跳过中间位置，每次滑动多个元素。</br>\n",
    "我们将每次滑动元素的数量称为步幅（stride）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([4, 4])"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conv2d=nn.Conv2d(1,1,kernel_size=3,padding=1,stride=2)\n",
    "X=X.reshape(1,1,8,8)\n",
    "#整除，3.5取3\n",
    "\"\"\"[（8+2x1-3）/2]+1\"\"\"\n",
    "conv2d(X).shape[2:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "更复杂一些的例子"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2, 2])"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conv2d=nn.Conv2d(1,1,kernel_size=(3,5),padding=(0,1),stride=(3,4))\n",
    "#同样还是整除，向下取整\n",
    "#填充后(8,10)\n",
    "#行：[(8-3)/3]+1=2\n",
    "#列[(10-5)/4]+1=2\n",
    "conv2d(X).shape[2:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "DL_pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
